use super::{FetchFromBytes, FromBytes, HttpBody, HttpParseError, HttpReceiveError, HttpSendError, IntoBytes};

mod builder;
mod header;
mod line;

pub use header::HttpResponseHeaders;
pub use line::HttpResponseLine;
pub use builder::HttpResponseBuilder;
use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, BufReader};

#[derive(Debug, Default)]
pub struct HttpResponse {
    pub line: HttpResponseLine,
    pub headers: HttpResponseHeaders,
    pub body: Option<HttpBody>,
}

impl HttpResponse {
    pub fn builder() -> HttpResponseBuilder {
        HttpResponseBuilder::default()
    }

    pub fn new(line: HttpResponseLine, headers: HttpResponseHeaders, body: Option<HttpBody>) -> Self {
        Self { line, headers, body }
    }

    pub fn not_found() -> Self {
        Self::builder()
            .status(404)
            .reason("Not Found")
            .build()
            .unwrap()
    }

    pub fn ok() -> Self {
        Self::builder()
            .status(200)
            .reason("ok")
            .build()
            .unwrap()
    }

    pub fn redirect<S: Into<String>>(location: S) -> Self {
        Self::builder()
            .status(302)
            .reason("Found")
            .location(location)
            .build()
            .unwrap()
    }

    pub fn default_reason(status: u16) -> &'static str {
        match status {
            200 => "OK",
            201 => "Created",
            204 => "No Content",
            301 => "Moved Permanently",
            302 => "Found",
            400 => "Bad Request",
            401 => "Unauthorized",
            403 => "Forbidden",
            404 => "Not Found",
            418 => "I'm a teapot",
            500 => "Internal Server Error",
            502 => "Bad Gateway",
            503 => "Service Unavailable",
            504 => "Gateway Timeout",
            _ => "Unknown",
        }
    }
}

impl HttpResponse {
    pub async fn receive_from<S>(stream: &mut S) -> Result<Option<Self>, HttpReceiveError> 
    where 
        S: AsyncRead + Unpin
    {
        let mut reader = BufReader::new(stream);
        let mut buffer = Vec::new();

        loop {
            let mut line = Vec::new();
            if reader.read_until(b'\n', &mut line).await? == 0 {
                return Ok(None);
            }
            buffer.extend(line.into_iter());
            if buffer.windows(4).any(|w| w == b"\r\n\r\n") {
                break;
            }
        }

        let (buffer, line) = HttpResponseLine::fetch_from_bytes(&buffer)?;
        let (_, headers) = HttpResponseHeaders::fetch_from_bytes(buffer)?;

        let content_length = headers.content_length.unwrap_or(0);
        let mut body_buf = vec![0u8; content_length as usize];

        let body = if content_length > 0 {
            reader.read_exact(&mut body_buf).await?;
            Some(HttpBody::from_bytes(body_buf))
        } else {
            None
        };

        Ok(Some(Self::new(line, headers, body)))
    }

    pub async fn send_to<S>(&self, stream: &mut S) -> Result<(), HttpSendError> 
    where 
        S: AsyncWrite + Unpin
    {
        let bytes = self.to_bytes();
        stream.write_all(&bytes).await?;
        Ok(())
    }
}

impl FromBytes for HttpResponse {
    type Err = HttpParseError;
    fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Err> {
        /*
            [v] [s] [p]
            [xx: xx   ]
            [xx: xx   ]

            [   body  ]
        */
        let (bytes, line) = HttpResponseLine::fetch_from_bytes(&bytes)?;
        let (bytes, headers) = HttpResponseHeaders::fetch_from_bytes(bytes)?;

        // Body
        let body = if bytes.len() == 0 {
            None
        } else {
            let content_length = headers.content_length;
            let body = match content_length {
                Some(content_length) => HttpBody::from_bytes(&bytes[..(content_length as usize)]),
                None => HttpBody::from_bytes(bytes),
            };
            Some(body)
        };

        Ok(Self { line, headers, body })
    }
}

impl IntoBytes for HttpResponse {
    fn to_bytes(&self) -> Vec<u8> {
        let mut bytes = Vec::new();
        
        bytes.extend(self.line.to_bytes());
        bytes.extend(self.headers.to_bytes());
        if let Some(body) = &self.body {
            bytes.extend(body.to_bytes());
        }

        bytes 
    }
}